package org.gap.registry;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * A high-speed storage location, used to store instances of
 * objects within a context and able to be located by a class.
 * 
 * @author gpelcha
 */
public class Registry {
	private static Map<Class<?>, Container<?>> bin;
	private static ContextLookup strategy;
	
	static {
		bin = new LinkedHashMap<Class<?>, Container<?>>();
	}
	
	/**
	 * Specifies the strategy used to identify the current context
	 * for performing a lookup of referenced instances.
	 * 
	 * @param strategy to identify the current active context
	 */
	public static void setContextStrategy(ContextLookup strategy) {
		Registry.strategy = strategy;		
	}
	
	/**
	 * Registers an instance with the given reference within the context.
	 * @param <T>
	 * @param context
	 * @param reference
	 * @param instance
	 */
	public static <T> void register(Object context, Class<T> reference, T instance) {
		 ensureContextLookup(reference);
		 getContextLookup(reference).set(context, instance);
	}

	/**
	 * Registers an instance with the given reference in the default context.
	 * 
	 * @param <T>
	 * @param reference
	 * @param instance
	 */
	public static <T> void register(Class<T> reference, T instance) {
		register(null, reference, instance);
	}
	
	/**
	 * Used to get the active context's registered instance for the given 
	 * reference.  If the current context does not contain a referenced instance
	 * the default context will be used instead.
	 * 
	 * @param <T>
	 * @param reference
	 * @return the referenced object under the given context, else the default referenced
	 * object.  Can return null.
	 */
	public static <T> T get(Class<T> reference) {
		return getContextLookup(reference).lookup();
	}
	
	/**
	 * Used to removed a referenced instance within the given context.  Will not
	 * remove any instances from other context or the default context.
	 * 
	 * @param <T>
	 * @param context
	 * @param reference
	 */
	public static <T> void unregister(Object context, Class<T> reference) {
		getContextLookup(reference).remove(context);
	}
	
	/**
	 * Removes the reference contained in the default context only.  All other references
	 * other will not be changed.
	 * 
	 * @param <T>
	 * @param reference
	 */
	public static <T> void unregister(Class<T> reference) {
		unregister(null, reference);
	}
	
	/**
	 * Determines if the current reference has an instance.
	 * @param <T>
	 * @param reference
	 * @return
	 */
	public static <T> boolean has(Class<T> reference) {
		return getContextLookup(reference).lookup() != null;
	}
	
	/**
	 * Clears any and all registered referred instances.
	 */
	public static void clear() {
		bin.clear();
	}
	
	private static <T> void ensureContextLookup(Class<T> reference) {
		if (!has(reference)) {
			 bin.put(reference, new Container<T>());
		 }
	}
	
	@SuppressWarnings("unchecked")
	private static <T> Lookup<T> getContextLookup(Class<T> reference) {
		Lookup<T> result;
		
		if (bin.containsKey(reference))
			result = ((Container<T>)bin.get(reference));
		else
			result = new DummyContextLookup<T>();
		
		return result;
	}
	
	/**
	 * Identifies which instance to return when a lookup is requested.
	 * 
	 * @author gpelcha
	 * @param <E>
	 */
	private interface Lookup<E> {
		public void set(Object context, E instance);
		public void remove(Object context);
		public E lookup();
	}
	
	/**
	 * Implementation of the {@link Lookup} interface.
	 * 
	 * @author gpelcha
	 * @param <E>
	 */
	private static class Container<E> implements Lookup<E>{
		private Map<Object, E> contextMap;
		
		public Container() {
			contextMap = new LinkedHashMap<Object, E>();
		}
		
		/**
		 * Sets the instance to the given context.
		 * 
		 * @param context
		 * @param instance
		 */
		public void set(Object context, E instance) {
			contextMap.put(context, instance);
		}
		
		/**
		 * Removes the instance based on the context.
		 * @param context
		 */
		public void remove(Object context) {
			contextMap.remove(context);
		}
		
		/**
		 * Performs a lookup for the requested instance based on
		 * the active context.  If no instance is found in the given
		 * context the default context is checked.
		 * 
		 * @return the instance in the context, unless null, then the 
		 * default context instance is returned.
		 */
		public E lookup() {
			E result = null;
			
			if (strategy != null) {
				result = contextMap.get(strategy.getContext());
			}
			
			if (result == null) {
				result = contextMap.get(null);
			}
			return result;
		}
	}

	/**
	 * Used to make the code more flexible.  Use this when no {@link Lookup}
	 * is registered in the bin and one is requested.
	 * @author gpelcha
	 *
	 * @param <E>
	 */
	private static class DummyContextLookup<E> implements Lookup<E> {
		public E lookup() {			return null;		}
		public void set(Object context, E instance) {		}
		public void remove(Object context) {		}
	}
}
